package org.rslai.autotest.core.page.impl;

import java.util.LinkedList;
import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.rslai.autotest.core.page.AbstractHelper;
import org.rslai.autotest.core.page.Table;

/**
 * 保存所有table数据
 * @author rslai
 */
class TableData {
	private LinkedList<LinkedList<WebElement>> table = new LinkedList<LinkedList<WebElement>>(); // 表
	private LinkedList<WebElement> currentRow; // 当前行数据

	/**
	 * 构造方法
	 */
	public TableData() {
		this.newRow();
	}

	/**
	 * 新增一行，当添加完一行数据时调用此方法
	 */
	public void newRow() {
		this.currentRow = new LinkedList<WebElement>();
		this.table.add(this.currentRow);
	}

	/**
	 * 增加一个单元格内容
	 * @param cell 单元格对象
	 */
	public void addCell(WebElement cell) {
		this.currentRow.add(cell);
	}

	/**
	 * 返回表格所有数据
	 * @return 所有数据
	 */
	public LinkedList<LinkedList<WebElement>> getTable() {
		return this.table;
	}

	/**
	 * 返回一行<br>
	 * 例：getRow(0); 返回表格的第1行数据
	 * @param row 行号, 从0开始
	 * @return 一组WebElement
	 */
	public LinkedList<WebElement> getRow(int row) {
		if (this.table.size() <= row || row == -1) {
			throw new NoSuchElementException("在表格中没有第 " + row + " 行数据，表格数据如下：\r\n" + this.toString());
		}
		return this.table.get(row);
	}

	/**
	 * 返回一个单元格<br>
	 * 例：getCell(0, 0); 返回表格的第1行第1列数据
	 * @param row 行号, 从0开始
	 * @param column 列号, 从0开始
	 * @return WebElement对象
	 */
	public WebElement getCell(int row, int column) {
		String errMsg = "在表格中没有第 " + row + " 行第 " + column + " 列数据，表格数据如下：\r\n";
		if (this.table.size() <= row || row == -1) {
			throw new NoSuchElementException(errMsg + this.toString());
		}
		LinkedList<WebElement> tmpRowData = this.table.get(row);
		if (tmpRowData.size() <= column || column == -1) {
			throw new NoSuchElementException(errMsg + this.toString());
		}
		return tmpRowData.get(column);
	}
	
	/**
	 * 覆写 toString() 方法
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer();

		sb.append("==================== Table ====================\r\n");
		for (LinkedList<WebElement> row : this.table) {
			for (WebElement cell : row) {
				sb.append(cell.getText() + " | ");
			}
			sb.append("\r\n");
		}

		return sb.toString();
	}

}

/**
 * 表格处理方法
 * @author rslai
 */
public class TableImpl extends AbstractHelper implements Table{
	private TableData tableData;

	@Override
	public String getCellText(int matchColumn, String matchStr, int column) {
		return getCell(matchColumn, matchStr, column).getText();
	}

	@Override
	public WebElement getCell(int matchColumn, String matchStr, int column) {
		return getCells(true, matchColumn, matchStr, column).get(0);
	}

	@Override
	public LinkedList<WebElement> getCells(int matchColumn, String matchStr, int column) {
		return getCells(false, matchColumn, matchStr, column);
	}

	/**
	 * 根据匹配内容返回一列  WebElement 对象
	 * @param singleColumn 是否单个单元格匹配，如果只想返回第一个匹配到的单元格数据设置为trun
	 * @param matchColumn 按第几列匹配，从0开始。例：如果想匹配第0列内容为“添加”此参数设置为 0
	 * @param matchStr 匹配字符串，例：如果想匹配第0列内容为“添加”此参数设置为 "添加"
	 * @param column 返回列号, 从0开始
	 * @return 一组 WebElement 对象，注：如果要在这个WebElement上使用 xpath 搜索，要在前边加 child::node() 例：By.xpath("child::node()//span[@class='del']")
	 */
	private LinkedList<WebElement> getCells(boolean singleColumn, int matchColumn, String matchStr, int column) {
		if (this.tableData == null) {
			throw new NullPointerException("表格数据为空，请先调用 setTableData 方法设置表格数据。");
		}

		LinkedList<WebElement> found = new LinkedList<WebElement>();
		if (matchColumn >= 0 && column >= 0 ) {
			LinkedList<LinkedList<WebElement>> bodyRows = this.tableData.getTable();
			for (LinkedList<WebElement> row : bodyRows) {
	
				if (row.size() > matchColumn) {
					matchStr = org.rslai.util.string.Convert.getInstance().removeBlank(matchStr);
					String cellStr = org.rslai.util.string.Convert.getInstance().removeBlank(row.get(matchColumn).getText());
					if (cellStr.equals(matchStr)) {
						if (row.size() > column) {
							found.add(row.get(column));	
							if (singleColumn) {
								return found;
							}
						}
					}
				}
			}
		}

		if (found.isEmpty()) {
			throw new NoSuchElementException("在表格中未找到第 " + matchColumn + " 列为 “" + matchStr + "” 的数据，或者这些数据不存在第 " + column + " 列(注意：从0开始)。表格数据如下：\r\n" + tableData.toString());
		} else {
			return found;
		}
	}

	@Override
	public LinkedList<WebElement> getCells(int column) {
		if (this.tableData == null) {
			throw new NullPointerException("表格数据为空，请先调用 setTableData 方法设置表格数据。");
		}

		LinkedList<WebElement> found = new LinkedList<WebElement>();
		if (column >= 0 ) {
			LinkedList<LinkedList<WebElement>> bodyRows = tableData.getTable();
			for (LinkedList<WebElement> row : bodyRows) {
				if (row.size() > column) {
					found.add(row.get(column));
				}
			}
		}

		if (found.isEmpty()) {
			throw new NoSuchElementException("在表格中未找到第 " + column + " 列数据(注意：从0开始)，表格数据如下：\r\n" + tableData.toString());
		} else {
			return found;
		}
	}

	@Override
	public LinkedList<WebElement> getRow(int matchColumn, String matchStr) {
		return this.getRows(true, matchColumn, matchStr).get(0);
	}

	@Override
	public LinkedList<LinkedList<WebElement>> getRows(int matchColumn, String matchStr) {
		return this.getRows(false, matchColumn, matchStr);
	}

	/**
	 * 根据匹配内容返回所有行的  WebElement 对象
	 * @param singleRow 是否单行匹配，如果只想返回第一行匹配到的数据设置为trun
	 * @param matchColumn 按第几列匹配，例：如果想匹配第3列内容为“添加”此参数设置为 3
	 * @param matchStr 匹配字符串，例：如果想匹配第3列内容为“添加”此参数设置为 "添加"
	 * @return 一组 WebElement 对象，注：如果要在这个WebElement上使用 xpath 搜索，要在前边加 child::node() 例：By.xpath("child::node()//span[@class='del']")
	 */
	private LinkedList<LinkedList<WebElement>> getRows(boolean singleRow, int matchColumn, String matchStr) {
		if (this.tableData == null) {
			throw new NullPointerException("表格数据为空，请先调用 setTableData 方法设置表格数据。");
		}

		LinkedList<LinkedList<WebElement>> found = new LinkedList<LinkedList<WebElement>>();
		if (matchColumn >=0 ) {
			LinkedList<LinkedList<WebElement>> bodyRows = tableData.getTable();
			for (LinkedList<WebElement> row : bodyRows) {
	
				if (row.size() > matchColumn) {
					matchStr = org.rslai.util.string.Convert.getInstance().removeBlank(matchStr);
					String cellStr = org.rslai.util.string.Convert.getInstance().removeBlank(row.get(matchColumn).getText());
					if (cellStr.equals(matchStr)) {
						found.add(row);
						if (singleRow) {
							return found;
						}
					}
				}
			}
		}
		if (found.isEmpty()) {
			throw new NoSuchElementException("在表格中未找到第 " + matchColumn + " 列为 “" + matchStr + "” 的数据(注意：从0开始)。表格数据如下：\r\n" + tableData.toString());
		} else {
			return found;
		}
	}

	@Override
	public LinkedList<LinkedList<WebElement>> getRows() {
		if (this.tableData == null) {
			throw new NullPointerException("表格数据为空，请先调用 setTableData 方法设置表格数据。");
		}

		return tableData.getTable();
	}

	@Override
	public LinkedList<WebElement> getRow(int row) {
		if (this.tableData == null) {
			throw new NullPointerException("表格数据为空，请先调用 setTableData 方法设置表格数据。");
		}

		return tableData.getRow(row);
	}

	@Override
	public String getCellText(int row, int column) {
		return this.getCell(row, column).getText();
	}

	@Override
	public WebElement getCell(int row, int column) {
		if (this.tableData == null) {
			throw new NullPointerException("表格数据为空，请先调用 setTableData 方法设置表格数据。");
		}

		return tableData.getCell(row, column);
	}

	@Override
	public Table setTableData(By by) {
		return this.setTableData(new By[]{by});
	}

	@Override
	public Table setTableData(By[] bys) {
		tableData = new TableData();

		for (By by : bys) {
			List<WebElement> tableList = this.driver().findElements(by);
			for (WebElement table : tableList) {
				List<WebElement> trList = table.findElements(By.tagName("tr"));
				for (int i=0; i<trList.size(); i++) {
					// List<WebElement> cellList = trList.get(i).findElements(By.xpath("*")); // 使用 * 取得所有元素会比写 th td 方式慢很多所以换用下面方法
					List<WebElement> cellList = trList.get(i).findElements(By.xpath("th | td"));
					for (WebElement e : cellList) {
						tableData.addCell(e); // 增加一个单元格内容
					}
					tableData.newRow(); // 新增一行
				}
			}
		}

		// 删除最后一次增加的空行
		if (!tableData.getTable().isEmpty()) {
			tableData.getTable().removeLast();
		}

		return this;
	}

}
